<!--
 * @Description  : 
 * @Version      : 1.0
 * @Author       : seraph
 * @Date         : 2024-12-26 12:19:10
 * @LastEditors  : seraph
 * @LastEditTime : 2025-02-19 13:53:03
-->
<template>
  <h3>声明响应式状态</h3>
  <p class="bolder">ref()</p>
  <p>在组合式 API 中，推荐使用 ref() 函数来声明响应式状态：</p>
  <pre><code class="javascript" v-html="highlighted1"></code></pre>

  <p>ref() 接收参数，并将其包裹在一个带有 .value 属性的 ref 对象中返回：</p>
  <pre><code class="javascript" v-html="highlighted2"></code></pre>

  <p>要在组件模板中访问 ref，请从组件的 setup() 函数中声明并返回它们：</p>
  <pre><code class="javascript" v-html="highlighted3"></code></pre>

  <h4> 在模板中访问 ref </h4>
  <pre><code class="html" v-html="highlighted4"></code></pre>
  <p>在模板中访问 ref，需要用 {&#123; &#125;} （双花括号）将其包裹起来。<span class="red bolder">不需要附加 .value</span>。因为在模板中使用时，ref 会自动解包。
  </p>
  <p>count value is: <span class="red bolder">{{ count }} </span><button @click="count++">add me</button></p>
  <!-- ================================================================================= -->
  <h4>注意事项：</h4>
  <p>在模板渲染上下文中，只有顶级的 ref 属性才会被解包。</p>
  <pre><code class="javascript" v-html="highlighted5"></code></pre>
  <!-- 这里编辑器会提示错误，请忽略！ -->
  <p><span class="red bolder">{{ count + 1 }}</span>, {{ objA.item1 + 1 }}</p>
  <hr>
  <h3>深层响应性</h3>
  <p>Ref 可以持有任何类型的值，包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构，比如 Map。</p>
  <p>Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时，变化也会被检测到：</p>
  <pre><code class="javascript" v-html="highlighted6"></code></pre>
  <p>非原始值将通过 reactive() 转换为响应式代理，该函数将在后面讨论。</p>
  <p>也可以通过 shallow ref 来放弃深层响应性。对于浅层 ref，只有 .value 的访问会被追踪。浅层 ref 可以用于避免对大型数据的响应性开销来优化性能、或者有外部库管理其内部状态的情况。</p>
  <hr>
  <h3>DOM 更新时机 </h3>
  <p>当你修改了响应式状态时，DOM 会被自动更新。但是需要注意的是，DOM 更新不是同步的。Vue 会在“next tick”更新周期中缓冲所有状态的修改，以确保不管你进行了多少次状态修改，每个组件都只会被更新一次。</p>
  <p>要等待 DOM 更新完成后再执行额外的代码，可以使用 nextTick() 全局 API：</p>
  <pre><code class="javascript" v-html="highlighted7"></code></pre>
  <p>在这个例子中，increment() 函数会先将 count 自增，然后打印当前值。然后它调用 nextTick() 函数，告诉 Vue 等待下一个更新周期，然后在那个周期里打印一个消息，表示 DOM 已经更新。</p>
  <p>注意：不要在 nextTick() 回调函数中触发状态修改，因为它会导致死循环。</p>
  <button @click="handleClick">Click me</button>
  <p>{{ count }}</p>
  <hr>
  <p>可以通过 shallow ref 来放弃深层响应性。对于浅层 ref，只有 .value 的访问会被追踪。浅层 ref 可以用于避免对大型数据的响应性开销来优化性能、或者有外部库管理其内部状态的情况。</p>
  <pre>
    <cdoe class="javascript" v-html="highlighted8"></cdoe>
  </pre>
  <p>在这个例子中，修改 objB 中的 id、item 以及 item 中的 count 时，不会被追踪，因为它 (obj) 是一个浅层 ref【shallowRef】。</p>
  <p>只有修改 objB 的 value 才会被追踪，即 objB 的顶层属性的变化才可以被追踪到。</p>
  <p>即使开启了深度监听，其内部深度变化也不会被追踪到，因为 watch() 的深度监听选项，只针对<span class="red bolder">普通响应式对象</span>。</p>
  <h4><span>{{ objB }}</span>
    <button @click="changeShallowValue">修改顶层数据</button>
    <button @click="changeDeepValue">修改深层数据</button> <button @click="changeObjValue">修改整个对象</button>
    <input type="checkbox" v-model="flag">深度监听</input>
  </h4>
  <p>监听情况：<span>{{ msg }}</span></p>
</template>

<script lang="ts" setup>
import { ref, reactive, nextTick, shallowRef, watch } from 'vue';
import hljs from 'highlight.js';
import 'highlight.js/styles/atom-one-dark.css';
import html from 'highlight.js/lib/languages/xml';
import javascript from 'highlight.js/lib/languages/javascript';

// 注册语言
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('html', html);

const rawCode1 = `import { ref } from 'vue'
const count = ref(0) `

const highlighted1 = ref(
  hljs.highlight(rawCode1, { language: 'javascript' }).value
    .replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;')
)

const rawCode2 = `const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1`

const highlighted2 = ref(
  hljs.highlight(rawCode2, { language: 'javascript' }).value
    .replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;')
)

const rawCode3 = `import { ref } from 'vue'

export default {
  // setup 是一个特殊的钩子，专门用于组合式 API。
  setup() {
    const count = ref(0)

    // 将 ref 暴露给模板
    return {
        count
    }
  }
}`

const highlighted3 = ref(
  hljs.highlight(rawCode3, { language: 'javascript' }).value
    .replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;')
)


const rawCode4 = `<div>{{ count }}</div>`

const highlighted4 = ref(
  hljs.highlight(rawCode4, { language: 'html' }).value
    .replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;')
)

const rawCode5 = `const count = ref(0)
const object = { item1: ref(0) }

{{ count + 1 }} // 可以正常解包，结果为 1
{{ objA.item1 + 1 }} // 不能正常解包，结果为 [object Object]1`

const highlighted5 = ref(
  hljs.highlight(rawCode5, { language: 'javascript' }).value
    .replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;')
)

const rawCode6 = `import { ref } from 'vue'

const obj = ref({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})

function mutateDeeply() {
  // 以下都会按照期望工作
  obj.value.nested.count++
  obj.value.arr.push('baz')
}`

const highlighted6 = ref(
  hljs.highlight(rawCode6, { language: 'javascript' }).value
    .replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;')
)

const rawCode7 = `import { ref, nextTick } from 'vue'

const count = ref(0)

function increment() {
  count.value++
  console.log('count is:', count.value)
  nextTick(() => {
  console.log('DOM updated')
  })
}`

const highlighted7 = ref(
  hljs.highlight(rawCode7, { language: 'javascript' }).value
    .replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;')
)

const rawCode8 = `import { ref, shallowRef } from 'vue'

const objB = shallowRef({
  id=0,
  item: { count: 0 }
})`

const highlighted8 = ref(
  hljs.highlight(rawCode8, { language: 'javascript' }).value
    .replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;')
)

const myButton = ref<HTMLButtonElement | null>(null);   // 模板引用

// 声明响应式状态
const count = ref(0);

const objA = ref({
  item1: { count: 0 }
})

const objB = shallowRef({
  id: 0,
  item: { count: 0 }
});


const handleClick = () => {
  count.value++;
  console.log('count is:', count.value);
  nextTick(() => {
    console.log('DOM updated');
  });
}

const changeShallowValue = () => {
  count.value++;
  objB.value.id = count.value;
  console.log('修改顶层数据:', objB.value);
}

const changeDeepValue = () => {
  count.value++;
  objB.value.item.count = count.value;
  console.log('修改深层数据:', objB.value);
}

const changeObjValue = () => {
  count.value++;
  objB.value = {
    id: count.value,
    item: { count: count.value }
  };
  console.log('修改整个对象:', objB.value);
}

const msg = ref();
const flag = ref(false);
watch(objB,
  (newValue: any, oldValue: any) => {
    msg.value = `objB 被修改: ${JSON.stringify(newValue)} ${JSON.stringify(oldValue)}`;
    console.log('objB 被修改:', newValue, oldValue);
  },
  {
    deep: flag.value,
  }
);
</script>

<style scoped>
p {
  font-size: 16px;
  padding: 2px;
  margin: 5px 0;
}

/* 设置预格式化文本的样式 */
pre {
  /* 背景色 */
  background-color: #f1ffed;
  /* 边框 */
  border: 1px solid #ccc;
  /* 缩小内边距 */
  padding: 5px 5px 5px 10px;
  /* 允许滚动 */
  overflow: auto;
  /* 可选：设置上下外边距 */
  margin: 0px 0;
  /* 允许换行 */
  /* normal: 连续的空白字符会被合并为一个空格。文本会在允许的断点处自动换行。
     nowrap：连续的空白字符会被合并为一个空格。文本不会自动换行，即使在允许的断点处也不会换行。
     pre：保留所有的空白字符（包括空格、制表符和换行符）。文本只会在遇到换行符或者 <br> 标签时换行。
     pre-wrap：保留所有的空白字符。文本会在遇到换行符、<br> 标签，或者达到容器边界时换行。
     pre-line：合并连续的空白字符为一个空格，但保留换行符。文本会在遇到换行符、<br> 标签，或者达到容器边界时换行。
   */
  white-space: pre-wrap;
  /* 行高 */
  line-height: 120%;
  /* 定义缩进 */
  tab-size: 4;
}

h3,
h4,
h5,
h6 {
  margin: 25px 0 10px 0;
  line-height: 100%;
}

.bolder {
  font-weight: bolder;
}

.red {
  color: red;
}

button {
  margin: 5px 5px 5px 10px;
  line-height: 150%;
  width: auto;
  padding: 0 10px;
}
</style>